package com.binance.assetservice.web.utils;


import java.io.IOException;
import java.net.URI;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.websocket.*;

import com.alibaba.fastjson.JSON;
import com.github.rholder.retry.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Market data Client
 *
 * @author roger
 */
@ClientEndpoint
public class WebsocketClientEndpoint {
    private static final WebSocketContainer CONTAINER = ContainerProvider.getWebSocketContainer();
    private static final Retryer<Integer> retryer =
            RetryerBuilder.<Integer>newBuilder().retryIfExceptionOfType(Throwable.class)
                    .withWaitStrategy(WaitStrategies.exponentialWait(64, TimeUnit.SECONDS))
                    .withStopStrategy(StopStrategies.neverStop()).build();
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final URI endpointURI;
    private volatile Session session = null;
    private MessageHandler messageHandler;

    public WebsocketClientEndpoint(URI endpointURI) {
        this.endpointURI = endpointURI;
    }

    public void connect() {
        try {
            logger.info("Start connection");
            retryer.call(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    try {
                        logger.info("Connecting to websocket server: {}", endpointURI.toString());
                        CONTAINER.connectToServer(WebsocketClientEndpoint.this, endpointURI);
                        logger.info("after connecting to websocket server");
                        return null;
                    } catch (Throwable th) {
                        logger.error("connect to websocket server error ", th);
                        throw th;
                    }
                }
            });
        } catch (ExecutionException e) {
            logger.error("connect to websocket server error ", e);
        } catch (RetryException e) {
            logger.error("connect to websocket server error ", e);
        }
    }

    public void close() {
        if (session != null) {
            logger.info("close session");
            try {
                session.close();
            } catch (IOException e) {
                logger.error("关闭链接错误:{}", e);
            } finally {
                session = null;
            }
        }
    }

    /**
     * Callback hook for Connection open events.
     *
     * @param session the session which is opened.
     */
    @OnOpen
    public void onOpen(Session session) {
        logger.info("opened websocket, session is {} ", session == null? "null": "not null");
        close();
        this.session = session;
        if (this.messageHandler != null) {
            this.messageHandler.handleOpenEvent(session);
        }
    }

    /**
     * Callback hook for Connection close events.
     *
     * @param session the session which is getting closed.
     * @param reason the reason for connection close
     */
    @OnClose
    public void onClose(Session session, CloseReason reason) {
        logger.info("closing websocket, close reason:{}", reason == null? "":JSON.toJSONString(reason));
        try {
            if(this.session != null) this.session.close();
        } catch (IOException e) {
            e.printStackTrace();
            logger.error("error happened: {}", e.getMessage());
        } finally {
            if ((reason == null || !CloseReason.CloseCodes.NORMAL_CLOSURE.equals(reason.getCloseCode()))
                    && this.messageHandler != null) {
                this.messageHandler.handleCloseEvent(session, reason);
            }
        }
    }

    /**
     * Callback hook for Message Events. This method will be invoked when a client send a message.
     *
     * @param message The text message
     */
    @OnMessage
    public void onMessage(String message) {
        if (this.messageHandler != null) {
            this.messageHandler.handleMessage(message);
        }
    }

    /**
     * register message handler
     *
     * @param msgHandler
     */
    public void addMessageHandler(MessageHandler msgHandler) {
        this.messageHandler = msgHandler;
    }

    /**
     * Send a message.
     *
     * @param message
     */
    public void sendMessage(String message) {
        this.session.getAsyncRemote().sendText(message);
    }

    /**
     * Message handler.
     *
     * @author roger
     */
    public static interface MessageHandler {

        public void handleMessage(String message);

        public void handleOpenEvent(Session session);

        public void handleCloseEvent(Session session, CloseReason reason);
    }


}
